"""
xapper Silverlight app definition class.
"""

from __future__ import unicode_literals

import os, zipfile
from contextlib import closing
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string

import xapper
from xapper.conf import settings


class SilverlightApp(object):
  """A Silverlight app definition."""

  def __init__(self, app_dir, assemblies=None, entry_point_assembly=None,
               entry_point_type=None):
    """Initialize a new SilverlightApp instance with information about a given
    Silverlight app.

    Args:
      app_dir     - (unicode) The relative path of the Silverlight app.  The
                    finders specified in the XAPPER_SILVERLIGHT_APP_FINDERS
                    setting are used to find the app directories.
      assemblies  - (list of unicode) The assembly file names that should be
                    included in the .xap file.
      entry_point_assembly - (unicode) The name of the assembly file in which the
                    Silverlight app entry point can be found.
      entry_point_type - (unicode) The type name of the Silverlight app entry
                    point class.
    """
    self.app_dir = app_dir
    self.assemblies = assemblies or settings.XAPPER_DEFAULT_ASSEMBLIES
    self.entry_point_assembly = entry_point_assembly \
        or settings.XAPPER_DEFAULT_ENTRY_POINT_ASSEMBLY
    self.entry_point_type = entry_point_type or settings.XAPPER_DEFAULT_ENTRY_POINT_TYPE

  def __unicode__(self):
    return unicode(self.name)

  def __str__(self):
    return str(self.name)

  def __repr__(self):
    return "<{0}: '{1}'>".format(type(self).__name__, self.name)

  def build_xap(self, outfile):
    """Build a .xap file for the Silverlight app.

    Args:
      outfile - (file-like object) The file into which the .xap file contents
                will be written.  This must be writable.

    Returns:
      None
    """
    with closing(zipfile.ZipFile(outfile, "w", zipfile.ZIP_DEFLATED, allowZip64=True)) as zf:
      try:
        abs_app_dir = tuple(xapper.finder.find_app_dir(self.app_dir))[0]
      except IndexError:
        raise ValueError("Unknown Silverlight app directory '{0}'.".format(app_dir))
      app_files = (os.path.join(d, f) for d, _, fs in os.walk(abs_app_dir) for f in fs)
      for f in app_files:
        arcname = os.path.relpath(f, abs_app_dir)
        zf.write(f, arcname)
      for a in self.assemblies:
        # TODO: Check if the assemblyis in the Silverlight app dir
        try:
          asmfile = tuple(xapper.finder.find_assembly(a))[0]
        except IndexError:
          raise ImproperlyConfigured("Unable to find assembly '{0}'.  Is XAPPER_ASSEMBLY_DIRS set "
                                     "correctly?".format(a))
        zf.write(asmfile, a)
      context = {
        "assemblies": self.assemblies,
        "entry_point_assembly": self.entry_point_assembly,
        "entry_point_type": self.entry_point_type,
      }
      manifest = render_to_string("xapper/AppManifest.xaml", context)
      zf.writestr("AppManifest.xaml", manifest)

  @property
  def name(self):
    """A read-only property to retrieve the Silverlight app's full name.

    The app definition name is of the form "<django_app>.<silverlight_app>",
    where <django_app> is the name of the Django app to which the Silverlight
    app belongs and <silverlight_app> is the name of the variable in the Django
    app's `xaps` module that refers to the SilverlightApp instance.

    Returns:
      (unicode) The full Silverlight app name.
    """
    for django_appname in settings.INSTALLED_APPS:
      try:
        xaps_module = getattr(__import__(".".join((django_appname, "xaps")), {}, {}, []), "xaps")
      except ImportError as e:
        if unicode(e) != "No module named xaps":
          raise
      else:
        for name, value in xaps_module.__dict__.iteritems():
          if value is self:
            return ".".join((django_appname, name))
    raise ValueError("The SilverlightApp instance was not found in any of the installed Django "
                     "app's `xaps` modules.")

  @property
  def url(self):
    """A read-only property to retrieve the URL for at which the Silverlight
    app's .xap file can be found.
    """
    from xapper.views import xap
    return reverse(xap, args=(self.name,))

  @staticmethod
  def get_app(app_name):
    """Get a Silverlight app definition by name.

    The app definition name is of the form "<django_app>.<silverlight_app>",
    where <django_app> is the name of the Django app to which the Silverlight
    app belongs and <silverlight_app> is the name of the variable in the Django
    app's `xaps` module that refers to the SilverlightApp instance.

    Args:
      app_name - (unicode) The full Silverlight app name.

    Returns:
      (SilverlightApp instance) The Silverlight app definition.
    """
    django_appname, silverlight_appname = app_name.rsplit(".", 1)
    try:
      xaps_module = __import__("{0}.xaps".format(django_appname), {}, {}, [silverlight_appname])
    except ImportError as e:
      # Py3 should use exception chaining here.  Too bad this wasn't backported to 2.6.
      #
      # raise ValueError("Unable to import the `xaps` module from the Django app `{0}`."
      #                  .format(django_appname)) from e
      raise
    try:
      return getattr(xaps_module, silverlight_appname)
    except AttributeError:
      raise ValueError("The Silverlight app `{0}` was not found in the Django app `{0}`."
                       .format(silverlight_appname, django_appname))
